﻿mod test_app;
mod test_user;

use secrecy::SecretString;
use sqlx::{Connection, Executor, PgConnection, PgPool};
pub use test_app::*;
pub use test_user::*;
use wiremock::MockServer;
use zero2prod::{
    startup::{get_connection_pool, Application},
    telemetry::{get_subscriber, init_subscriber},
    DatabaseSettings,
};

use std::sync::LazyLock;
static TRACING: LazyLock<()> = LazyLock::new(|| {
    let default_filter_level = "info";
    let subscriber_name = "test";
    if std::env::var("TEST_LOG").is_ok() {
        let subscriber = get_subscriber(subscriber_name, default_filter_level, std::io::stdout);
        init_subscriber(subscriber);
    } else {
        let subscriber = get_subscriber(subscriber_name, default_filter_level, std::io::sink);
        init_subscriber(subscriber);
    }
});

// 启动应用程序实例，并返回监听地址。比如：http://localhost:8080
pub async fn spawn_app() -> TestApp {
    // 1
    // Lazy::force(&TRACING); // 只在第一次使用 TRACING 时会调用 initialize
    // 2
    // TRACING.get_or_init(|| init_subscriber(get_subscriber("test", "debug")));
    // 3
    LazyLock::force(&TRACING);

    let email_server = MockServer::start().await;

    // 为了确保测试的隔离性，随机化配置
    let configuration = {
        let mut c = zero2prod::get_configuration().expect("Failed to read configuration.");
        // 为每个测试使用不同的数据库
        c.database.database_name = uuid::Uuid::new_v4().to_string();
        // 使用系统提供的随机端口
        c.application.port = 0;
        // 使用模拟服务器作为邮件API
        c.email_client.base_url = email_server.uri();
        c
    };
    // 创建并迁移数据库
    configure_database(&configuration.database).await;
    // 后台启动应用
    let application = Application::build(&configuration)
        .await
        .expect("Failed to build application.");
    let port = application.port();
    tokio::spawn(application.run_until_stopped());

    // 默认情况下，reqwest 看到 303 状态码会自动重定向。Policy::none() 可以阻止重定向。
    let client = reqwest::Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .cookie_store(true)
        .build()
        .unwrap();

    let test_app = TestApp {
        port,
        address: format!("http://127.0.0.1:{}", port),
        db_pool: get_connection_pool(&configuration.database),
        email_server,
        test_user: TestUser::gerenate(),
        api_client: client,
        email_client: configuration.email_client.client(),
    };
    test_app.test_user.store(&test_app.db_pool).await;
    test_app
}

pub fn assert_is_redirect_to(response: &reqwest::Response, location: &str) {
    assert_eq!(response.status().as_u16(), 303);
    assert_eq!(response.headers().get("Location").unwrap(), location);
}

async fn configure_database(config: &DatabaseSettings) -> PgPool {
    // 创建数据库
    let maintenance_settings = DatabaseSettings {
        database_name: "".to_string(),
        ..config.clone()
    };
    let mut connection = PgConnection::connect_with(&maintenance_settings.connect_options())
        .await
        .expect("Failed to connect to Postgres.");
    connection
        .execute(format!(r#"CREATE DATABASE "{}";"#, config.database_name).as_str())
        .await
        .expect("Failed to create database");
    // 迁移数据库
    let db_pool = PgPool::connect_with(config.connect_options())
        .await
        .expect("Failed to connect to Postgres.");
    sqlx::migrate!("./migrations")
        .run(&db_pool)
        .await
        .expect("Failed to migrate the database.");
    db_pool
}
